<FrameworkSwitchCourse {fw} />

# Préparer les données

{#if fw === 'pt'}


<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}

En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec PyTorch :

```python
import torch
from torch.optim import AdamW
from transformers import AutoTokenizer, AutoModelForSequenceClassification

# Même chose que précédemment
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    # J'ai attendu un cours de HuggingFace toute ma vie.
    "This course is amazing!",  # Ce cours est incroyable !
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")

# Ceci est nouveau
batch["labels"] = torch.tensor([1, 1])

optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()
```
{:else}

En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec TensorFlow :

```python
import tensorflow as tf
import numpy as np
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

# Même chose que précédemment
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    # J'ai attendu un cours de HuggingFace toute ma vie.
    "This course is amazing!",  # Ce cours est incroyable !
]
batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf"))

# Ceci est nouveau
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
labels = tf.convert_to_tensor([1, 1])
model.train_on_batch(batch, labels)
```
{/if}
Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. 

Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. 

### Charger un jeu de données depuis le <i>Hub</i>

{#if fw === 'pt'}
<Youtube id="_BZearw7f0w"/>
{:else}
<Youtube id="W_gMJF0xomE"/>
{/if}

Le *Hub* ne contient pas seulement des modèles mais aussi plusieurs jeux de données dans un tas de langues différentes. Vous pouvez explorer les jeux de données [ici](https://huggingface.co/datasets) et nous vous conseillons d'essayer de charger un nouveau jeu de données une fois que vous avez étudié cette section (voir la documentation générale [ici](https://huggingface.co/docs/datasets/loading)). Mais pour l'instant, concentrons-nous sur le jeu de données MRPC ! Il s'agit de l'un des 10 jeux de données qui constituent le [*benchmark* GLUE](https://gluebenchmark.com/) qui est un *benchmark* académique utilisé pour mesurer les performances des modèles d'apprentissage automatique sur 10 différentes tâches de classification de textes.  

La bibliothèque 🤗 *Datasets* propose une commande très simple pour télécharger et mettre en cache un jeu de données à partir du *Hub*. On peut télécharger le jeu de données MRPC comme ceci :   

<Tip>
⚠️ **Attention** Assurez-vous que `datasets` est installé en exécutant `pip install datasets`. Ensuite, chargez le jeu de données MRPC et imprimez-le pour voir ce qu'il contient.
</Tip> 

```py
from datasets import load_dataset

raw_datasets = load_dataset("glue", "mrpc")
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 408
    })
    test: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 1725
    })
})
```

Comme vous le voyez, on obtient un objet de type `DatasetDict` qui contient le jeu de données d'entraînement, celui de validation et celui de test. Chacun d'eux contient plusieurs colonnes (`sentence1`, `sentence2`, `label` et `idx`) et une variable nombre de lignes qui contient le nombre d'éléments dans chaque jeu de données (il y a donc 3.668 paires de phrases dans le jeu d'entraînement, 408 dans celui de validation et 1.725 dans celui de test).

Cette commande télécharge et met en cache le jeu de données dans *~/.cache/huggingface/dataset*. Rappelez-vous que comme vu au chapitre 2, vous pouvez personnaliser votre dossier cache en modifiant la variable d'environnement `HF_HOME`.

Nous pouvons accéder à chaque paire de phrase de notre objet `raw_datasets` par les indices, comme avec un dictionnaire :

```py
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
```

```python out
{'idx': 0,
 'label': 1,
 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', 
 # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage.
 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} 
 # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage.
```

Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne :

```py
raw_train_dataset.features
```

```python out
{'sentence1': Value(dtype='string', id=None),
 'sentence2': Value(dtype='string', id=None),
 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
 'idx': Value(dtype='int32', id=None)}
```

En réalité, `label` est de type `ClassLabel` et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à  `not_equivalent` et `1` correspond à `equivalent`.

<Tip>

✏️ **Essayez !** Regardez l'élément 15 de l'ensemble d'entraînement et l'élément 87 de l'ensemble de validation. Quelles sont leurs étiquettes ?
</Tip>

### Prétraitement d'un jeu de données

{#if fw === 'pt'}
<Youtube id="0u3ioSwev3s"/>
{:else}
<Youtube id="P-rZWqcB6CE"/>
{/if}

Pour prétraiter le jeu de données, nous devons convertir le texte en chiffres compréhensibles par le modèle. Comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), cette conversion est effectuée par un *tokenizer*. Nous pouvons fournir au *tokenizer* une phrase ou une liste de phrases, de sorte que nous pouvons directement tokeniser toutes les premières phrases et toutes les secondes phrases de chaque paire comme ceci :

```py
from transformers import AutoTokenizer

checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
```

Cependant, nous ne pouvons pas simplement passer deux séquences au modèle et obtenir une prédiction pour savoir si les deux phrases sont des paraphrases ou non. Nous devons traiter les deux séquences comme une paire, et appliquer le prétraitement approprié. Heureusement, le *tokenizer* peut également prendre une paire de séquences et la préparer de la manière attendue par notre modèle BERT : 

```py
inputs = tokenizer(
    "This is the first sentence.", "This is the second one."
)  # "C'est la première phrase.", "C'est la deuxième."
inputs
```

```python out
{ 
  'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
  'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
  'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
```

Nous avons discuté des clés `input_ids` et `attention_mask` dans le [chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase.

<Tip>

✏️ **Essayez !** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et par paire. Quelle est la différence entre les deux résultats ?

</Tip>

Si on décode les IDs dans `input_ids` en mots :

```py
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
```

nous aurons :

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
```

Nous voyons donc que le modèle s'attend à ce que les entrées soient de la forme `[CLS] phrase1 [SEP] phrase2 [SEP]` lorsqu'il y a deux phrases. En alignant cela avec les `token_type_ids`, on obtient :

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
[      0,      0,    0,     0,       0,          0,   0,       0,      1,    1,     1,        1,     1,   1,       1]
```

Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sentence1 [SEP]` ont toutes un *token* de type ID de `0`, tandis que les autres parties, correspondant à `sentence2 [SEP]`, ont toutes un *token* de type ID de `1`.

Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. 

Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases.

Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. 

En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle.

Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement :

```py
tokenized_dataset = tokenizer(
    raw_datasets["train"]["sentence1"],
    raw_datasets["train"]["sentence2"],
    padding=True,
    truncation=True,
)
```

Cela fonctionne bien, mais a l'inconvénient de retourner un dictionnaire (avec nos clés, `input_ids`, `attention_mask`, et `token_type_ids`, et des valeurs qui sont des listes de listes). Cela ne fonctionnera également que si vous avez assez de RAM pour stocker l'ensemble de votre jeu de données pendant la tokenisation (alors que les jeux de données de la bibliothèque 🤗 *Datasets* sont des fichiers [Apache Arrow](https://arrow.apache.org/) stockés sur le disque, vous ne gardez donc en mémoire que les échantillons que vous demandez).

Pour conserver les données sous forme de jeu de données, nous utiliserons la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Cela nous permet également une certaine flexibilité, si nous avons besoin d'un prétraitement plus poussé que la simple tokenisation. La méthode `map()` fonctionne en appliquant une fonction sur chaque élément de l'ensemble de données, donc définissons une fonction qui tokenise nos entrées :

```py
def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
```

Cette fonction prend un dictionnaire (comme les éléments de notre jeu de données) et retourne un nouveau dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`. Notez que cela fonctionne également si le dictionnaire `example` contient plusieurs échantillons (chaque clé étant une liste de phrases) puisque le `tokenizer` travaille sur des listes de paires de phrases, comme vu précédemment. Cela nous permettra d'utiliser l'option `batched=True` dans notre appel à `map()`, ce qui accélérera grandement la tokénisation. Le `tokenizer` est soutenu par un *tokenizer* écrit en Rust à partir de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers). Ce *tokenizer* peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps.

Notez que nous avons laissé l'argument `padding` hors de notre fonction de *tokenizer* pour le moment. C'est parce que le *padding* de tous les échantillons à la longueur maximale n'est pas efficace : il est préférable de remplir les échantillons lorsque nous construisons un batch, car alors nous avons seulement besoin de remplir à la longueur maximale dans ce batch, et non la longueur maximale dans l'ensemble des données. Cela peut permettre de gagner beaucoup de temps et de puissance de traitement lorsque les entrées ont des longueurs très variables ! 

Voici comment nous appliquons la fonction de tokenization sur tous nos jeux de données en même temps. Nous utilisons `batched=True` dans notre appel à `map` pour que la fonction soit appliquée à plusieurs éléments de notre jeu de données en une fois, et non à chaque élément séparément. Cela permet un prétraitement plus rapide.

```py
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets
```

La façon dont la bibliothèque 🤗 *Datasets* applique ce traitement consiste à ajouter de nouveaux champs aux jeux de données, un pour chaque clé du dictionnaire renvoyé par la fonction de prétraitement :

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 408
    })
    test: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 1725
    })
})
```

Vous pouvez même utiliser le multitraitement lorsque vous appliquez votre fonction de prétraitement avec `map()` en passant un argument `num_proc`. Nous ne l'avons pas fait ici parce que la bibliothèque 🤗 *Tokenizers* utilise déjà plusieurs *threads* pour tokeniser nos échantillons plus rapidement, mais si vous n'utilisez pas un *tokenizer* rapide soutenu par cette bibliothèque, cela pourrait accélérer votre prétraitement.

Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les divisions de notre jeu de données. Notez que nous aurions également pu modifier des champs existants si notre fonction de prétraitement avait retourné une nouvelle valeur pour une clé existante dans l'ensemble de données auquel nous avons appliqué `map()`.

La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*.

### <i>Padding</i> dynamique

<Youtube id="7q5NyFT8REg"/>

{#if fw === 'pt'}
La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction d'assemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire.

{:else}
La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction d'assemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire.

{/if}
Pour faire cela en pratique, nous devons définir une fonction d'assemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin :

{#if fw === 'pt'}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```
{:else}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")
```
{/if}

Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des *strings* (et nous ne pouvons pas créer des tenseurs avec des *strings*) et on regarde la longueur de chaque entrée du batch : 

```py
samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
[len(x) for x in samples["input_ids"]]
```

```python out
[50, 59, 47, 67, 59, 50, 62, 32]
```

Sans surprise, nous obtenons des échantillons de longueur variable, de 32 à 67. Le *padding* dynamique signifie que les échantillons de ce batch doivent tous être rembourrés à une longueur de 67, la longueur maximale dans le batch. Sans le *padding* dynamique, tous les échantillons devraient être rembourrés à la longueur maximale du jeu de données entier, ou à la longueur maximale que le modèle peut accepter. Vérifions à nouveau que notre `data_collator` rembourre dynamiquement le batch correctement :

```py
batch = data_collator(samples)
{k: v.shape for k, v in batch.items()}
```

{#if fw === 'tf'}

```python out
{'attention_mask': TensorShape([8, 67]),
 'input_ids': TensorShape([8, 67]),
 'token_type_ids': TensorShape([8, 67]),
 'labels': TensorShape([8])}
```

{:else}

```python out
{'attention_mask': torch.Size([8, 67]),
 'input_ids': torch.Size([8, 67]),
 'token_type_ids': torch.Size([8, 67]),
 'labels': torch.Size([8])}
```

C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que notre modèle peut traiter, nous sommes prêts à le *finetuner* !

{/if}

<Tip>

✏️ **Essayez !** Reproduisez le prétraitement sur le jeu de données GLUE SST-2. C'est un peu différent puisqu'il est composé de phrases simples au lieu de paires, mais le reste de ce que nous avons fait devrait être identique. Pour un défi plus difficile, essayez d'écrire une fonction de prétraitement qui fonctionne sur toutes les tâches GLUE.

</Tip>

{#if fw === 'tf'}

Maintenant que nous disposons de notre jeu de données et d'un assembleur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données !

```py
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)

tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=False,
    collate_fn=data_collator,
    batch_size=8,
)
```

Et c'est tout ! Nous pouvons utiliser ces jeux de données dans le prochain cours, où l'entraînement sera agréablement simple après tout le dur travail de prétraitement des données.

{/if}
